using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using Random = UnityEngine.Random;

namespace AsyncTask
{
    public class ShapeManager : MonoBehaviour
    {
        [SerializeField] private Shape[] _shapes;
        [SerializeField] private TextMeshProUGUI _doneText;
        [SerializeField] private TextMeshProUGUI _randomText;

        private void Awake()
        {
            _doneText.gameObject.SetActive(false);
            _randomText.text = "";
        }

        public void BeginTest()
        {
            StartRotating();

            StartSequentialActions();
        }

        //////////////////////////////////////////////
        // PART1 : 执行某个持续一段时间的动作
        
        // /// <summary>
        // /// 使用协程，常规方式
        // /// </summary>
        // private void StartRotating()
        // {
        //     for (int i = 0; i < _shapes.Length; i++)
        //     {
        //         StartCoroutine(_shapes[i].RotateForSeconds(1 + 1 * i));
        //     }
        // }
        
        /// <summary>
        /// 使用异步，比协程实现起来更简单，代码结构也更简单
        /// </summary>
        private void StartRotating()
        {
            for (int i = 0; i < _shapes.Length; i++)
            {
                // _shapes[i].RotateForSeconds(1 + 1 * i);
                _shapes[i].RotateForSecondsAsync(1 + 1 * i);
            }
        }
        
        
        //////////////////////////////////////////////
        // PART2 : 需要多个动作连续执行，只能写一堆的Coroutine， 如果时间不确定时，将更难控制

        // void StartSequentialActions()
        // {
        //     StartCoroutine(SequentialCoroutines());
        // }
        //
        // IEnumerator SequentialCoroutines()
        // {
        //     yield return StartCoroutine(Action1());
        //     yield return StartCoroutine(Action2());
        //     yield return StartCoroutine(Action3());
        // }
        //
        // IEnumerator Action1()
        // {
        //     yield return new WaitForSeconds(4);
        //     _shapes[0].RotateForSeconds(1 + 1 * 0);
        // }
        //
        // IEnumerator Action2()
        // {
        //     yield return new WaitForSeconds(1);
        //     _shapes[1].RotateForSeconds(1 + 1 * 1);
        // }
        //
        // IEnumerator Action3()
        // {
        //     yield return new WaitForSeconds(1);
        //     _shapes[2].RotateForSeconds(1 + 1 * 2);
        // }
        
        async void StartSequentialActions()
        {
            await Task.Delay(4000);
            
            // 对Task不关心的处理方式，所有任务会同时执行
            // for (int i = 0; i < _shapes.Length; i++)
            // {
            //     // _shapes[i].RotateForSeconds(1 + 1 * i);
            //     await _shapes[i].RotateForSecondsAsyncTask(1 + 1 * i);
            // }
            
            
            //////////////////////////////////////////////
            // PART3 : 管理Task
            // 如果希望对Task进行管理，比如希望让所有Task完成之后做某事
            // 或者让Task并行，或者串行运行
            // 都可以先将Task记录起来，然后对Task进行管理调用
            // 如果相同的功能用协程来处理，那对流程修改会非常大
            
            var tasks = new List<Task>();
            for (int i = 0; i < _shapes.Length; i++)
            {
                // _shapes[i].RotateForSeconds(1 + 1 * i);
                // await _shapes[i].RotateForSecondsAsyncTask(1 + 1 * i);
                tasks.Add(_shapes[i].RotateForSecondsAsyncTask(1 + 1 * i));
            }

            // Parallel.ForEach(tasks, ...);   
            await Task.WhenAll(tasks);


            // 在这里会保证所有任务都已经完成， 使用同步的代码编写方式，实现异步的工作

            //////////////////////////////////////////////
            // PART4 : 如果在完成以上事情后需要回调执行某些事情
            // 那么回调函数只能一级级的传入，然后再一级级的传出云
            // 当不同层级的回调，还要做其它回调时，那完全是一堆糟，也称 Callback Hell
            // 而如果使用 async await 来实现，只需要优雅的在这后面实现相应逻辑，或者对关注的Task做对应操作即可

            ShowDoneText();
             
            //////////////////////////////////////////////
            // PART5 : 如果希望在任务执行完之后，返回某些内部变量
            // 这点使用协程是完成不可以做到的，只能在外部建立公共变量，由协程逻辑修改相应状态
            // 但这个过程是具有相当的不稳定性的，因为你不能保证是否有其它逻辑也修改了，更别说还有调用顺序问题
            // 而如果使用 async await 来实现，是可以直接返回数值的
            ShowReturnValues();
       }
        
        /// <summary>
        /// 完成以后显示文本，按常规的得继续加协程
        /// </summary>
        private void ShowDoneText()
        {
            _doneText.gameObject.SetActive(true);
        }

        async void ShowReturnValues()
        {
            for (int i = 0; i < 3; i++)
            {
                // 注意：这里的await非常重要，如果不写，将返回的是任务本身！ 也可以判断Task的状态，比如是否完成，是否被取消，是否出错，是否成功完成，并获取结果
                // 根据这些状态，还可以方便的控制Task，比如还能停止任务，以免任务运行时间过久等
                // var task = GetRandNumber();
                
                // 如果不使用 async 和 await，还可以使用以下的方式获取结果，此时方法中声明的 async 就可以去掉了
                // 很方便，是吧？
                // var randomNumber = task.GetAwaiter().GetResult();
                // 要注意，如果不写GetAwaiter，直接GetResult
                // 当GetRandNumber()内部出现异常时，这里返回的结果就不是想要的内容了
                
                // 特别说明： task.GetAwaiter().GetResult(); 调用时，在某些版本的Unity下好像会卡死？ 原因待定
                

                var randomNumber = await GetRandNumber();
                Debug.Log("ShowReturnValues:" + randomNumber);
                _randomText.text = $"Random:{randomNumber.ToString()}";
            }
        }

        async Task<int> GetRandNumber()
        {
            var randomNum = Random.Range(2000, 4000);
            await Task.Delay(randomNum);
            return randomNum;
        }
    }

}